#include <assert.h>
#include "winnt.h"
#include "stdafx.h"
#include "Profiler.h"

#include "sqlite3.h"

#pragma warning (disable: 4996) 

#define ARRAY_SIZE(s) (sizeof(s) / sizeof(s[0]))

// global reference to the profiler object (this) used by the static functions
CProfiler* g_pICorProfilerCallback = NULL;

sqlite3 *database = NULL;

int applicationId = -1;
int eventId = -1;

WCHAR voidString[5] = _T("NULL"); 

static int callback(void *NotUsed, int argc, char **argv, char **azColName){return 0;}

static int callbackLastIdApplication(void *NotUsed, int argc, char **argv, char **azColName){
	applicationId = atoi(argv[0]);
	return 0;
}

static int callbackLastIdEvent(void *NotUsed, int argc, char **argv, char **azColName){
	eventId = atoi(argv[0]);
	return 0;
}

// CProfiler
CProfiler::CProfiler() 
{
	//Initializing
	start=0.0;

	
	
	for (int i = 0; i <NUMFILES; i++)
		m_hLogFile[i] = INVALID_HANDLE_VALUE;
	m_callStackSize = 0;
	m_threadNumber = 0;
	m_fileNumber = 0;
	m_lines = 0;
}

CProfiler::~CProfiler()
{
	CloseHandle(_fakeLog);


	// clean up the critical section
	DeleteCriticalSection(&criticalSection);
}

HRESULT CProfiler::FinalConstruct()
{
	_fakeLog = CreateFile(L"C:\\test.log", FILE_APPEND_DATA, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	// create the log file
	CreateLogFile();

	//Loading settings from environment variables (here because I can use log for debugging)
	LoadSettings();

	// log that we have reached FinalConstruct
	LogString("[INFO] Entering FinalConstruct\r\n");

#ifdef VERBOSE_MODE
	LogString("[INFO] Verbose mode, more debug info provided...\r\n");
#endif

	return S_OK;
}

void CProfiler::FinalRelease()
{
	// log that we have reached FinalRelease
	LogString("[INFO] Entering FinalRelease\r\n");

	// close the log file
	CloseLogFile();
}

// ----  CALLBACK FUNCTIONS ------------------

// this function simply forwards the FunctionEnter call the global profiler object
void __stdcall FunctionEnterGlobal(FunctionID functionID, UINT_PTR clientData, COR_PRF_FRAME_INFO frameInfo, COR_PRF_FUNCTION_ARGUMENT_INFO *argInfo)
{
	// make sure the global reference to our profiler is valid
	if (g_pICorProfilerCallback != NULL)
		g_pICorProfilerCallback->Enter(functionID, clientData, frameInfo, argInfo);
}

// this function is called by the CLR when a function has been entered
void _declspec(naked) FunctionEnterNaked(FunctionID functionID, UINT_PTR clientData, COR_PRF_FRAME_INFO func, COR_PRF_FUNCTION_ARGUMENT_INFO *argumentInfo)
{
	__asm
	{
		push    ebp                 // Create a standard frame
		mov     ebp,esp
		pushad                      // Preserve all registers

		mov     eax,[ebp+0x14]      // argumentInfo
		push    eax
		mov     ecx,[ebp+0x10]      // func
		push    ecx
		mov     edx,[ebp+0x0C]      // clientData
		push    edx
		mov     eax,[ebp+0x08]      // functionID
		push    eax
		call    FunctionEnterGlobal

		popad                       // Restore all registers
		pop     ebp                 // Restore EBP
		ret     16
	}
}

// this function simply forwards the FunctionLeave call the global profiler object
void __stdcall FunctionLeaveGlobal(FunctionID functionID, UINT_PTR clientData, COR_PRF_FRAME_INFO frameInfo, COR_PRF_FUNCTION_ARGUMENT_RANGE *retvalRange)
{
	// make sure the global reference to our profiler is valid
	if (g_pICorProfilerCallback != NULL)
		g_pICorProfilerCallback->Leave(functionID,clientData,frameInfo,retvalRange);
}

// this function is called by the CLR when a function is exiting
void _declspec(naked) FunctionLeaveNaked(FunctionID functionID, UINT_PTR clientData, COR_PRF_FRAME_INFO func, COR_PRF_FUNCTION_ARGUMENT_RANGE *retvalRange)
{
	__asm
	{
		push    ebp                 // Create a standard frame
		mov     ebp,esp
		pushad                      // Preserve all registers

		mov     eax,[ebp+0x14]      // argumentInfo
		push    eax
		mov     ecx,[ebp+0x10]      // func
		push    ecx
		mov     edx,[ebp+0x0C]      // clientData
		push    edx
		mov     eax,[ebp+0x08]      // functionID
		push    eax
		call    FunctionLeaveGlobal

		popad                       // Restore all registers
		pop     ebp                 // Restore EBP
		ret     16
	}
}

// this function simply forwards the FunctionLeave call the global profiler object
void __stdcall FunctionTailcallGlobal(FunctionID functionID, UINT_PTR clientData, COR_PRF_FRAME_INFO frameInfo)
{
	if (g_pICorProfilerCallback != NULL)
		g_pICorProfilerCallback->Tailcall(functionID,clientData,frameInfo);
}

// this function is called by the CLR when a tailcall occurs.  A tailcall occurs when the 
// last action of a method is a call to another method.
void _declspec(naked) FunctionTailcallNaked(FunctionID functionID, UINT_PTR clientData, COR_PRF_FRAME_INFO func)
{
	__asm
	{
		push    ebp                 // Create a standard frame
		mov     ebp,esp
		pushad                      // Preserve all registers

		mov     eax,[ebp+0x14]      // argumentInfo
		push    eax
		mov     ecx,[ebp+0x10]      // func
		push    ecx
		mov     edx,[ebp+0x0C]      // clientData
		push    edx
		mov     eax,[ebp+0x08]      // functionID
		push    eax
		call    FunctionTailcallGlobal

		popad                       // Restore all registers
		pop     ebp                 // Restore EBP
		ret     16
	}
}

// ----  MAPPING FUNCTIONS ------------------

// this function is called by the CLR when a function has been mapped to an ID
UINT_PTR CProfiler::FunctionMapper(FunctionID functionID, BOOL *pbHookFunction)
{
	// make sure the global reference to our profiler is valid.  Forward this
	// call to our profiler object
	if (g_pICorProfilerCallback != NULL)
		g_pICorProfilerCallback->MapFunction(functionID);

	// we must return the function ID passed as a parameter
	return (UINT_PTR)functionID;
}

// the static function called by .Net when a function has been mapped to an ID
void CProfiler::MapFunction(FunctionID functionID)
{
	// see if this function is in the map
	CFunctionInfo* functionInfo = NULL;
	std::map<FunctionID, CFunctionInfo*>::iterator iter = m_functionMap.find(functionID);
	if (iter == m_functionMap.end())
	{
		WCHAR szMethodName[NAME_BUFFER_SIZE];
		const WCHAR* p = NULL;
		int isStatic;
		USES_CONVERSION;

		functionInfo = new CFunctionInfo(functionID);

		// get the method name
		HRESULT hr = GetMethodInfo(functionInfo); 
		
		if (FAILED(hr))
		{
			return;
		}

		if (!functionInfo->toExclude)
		{
			m_functionMap.insert(std::pair<FunctionID, CFunctionInfo*>(functionID, functionInfo));
		}
	}
}

void CProfiler::Enter(FunctionID functionID, UINT_PTR clientData, COR_PRF_FRAME_INFO frameInfo, COR_PRF_FUNCTION_ARGUMENT_INFO *argumentInfo)
{
	
	EnterCriticalSection(&criticalSection);

	ObjectID* objID;

	if (argumentInfo!= NULL && argumentInfo->numRanges > 0)
		objID=(ObjectID*)(argumentInfo->ranges[0].startAddress);
	else
		objID=NULL;

	CFunctionInfo* functionInfo = NULL;
	std::map<FunctionID, CFunctionInfo*>::iterator iterF = m_functionMap.find(functionID);
	if (iterF != m_functionMap.end())
	{

		functionInfo = (iterF->second);

		/*if(	!functionInfo->toExclude
			(wcsncmp(functionInfo->GetFuncName(),pszApplicationName,wcslen(pszApplicationName))==0) 
				&&	(wcsncmp(functionInfo->GetFuncName(),L"System",6)!=0) 
				&&	(wcsncmp(functionInfo->GetFuncName(),L"Microsoft",9)!=0) 
				)
		{*/
			functionInfo->IncrementCallCount();

			ThreadID threadID;

			m_pICorProfilerInfo2->GetCurrentThreadID(&threadID);

			std::map<long,int>::iterator iterT = m_ThreadMap.find(threadID);

			if (iterT != m_ThreadMap.end())
			{
				iterT->second++;

				if(functionInfo->GetFuncName() != NULL){

					char timeBuffer[20];
					GetTimestamp(timeBuffer);


					if (objID != NULL)
					{
						LogEvent(3,threadID,functionInfo->GetFuncName(),functionInfo->functionID,functionInfo->GetFuncName(),functionInfo->classID,*objID,iterT->second,timeBuffer,functionInfo->GetFuncName(),functionInfo->isStatic);
					}
					else
					{
						LogEvent(3,threadID,functionInfo->GetFuncName(),functionInfo->functionID,functionInfo->GetFuncName(),functionInfo->classID,-1,iterT->second,timeBuffer,functionInfo->GetFuncName(),functionInfo->isStatic);
					}
				}
			}

		//}
	}
	
	LeaveCriticalSection(&criticalSection);
}

// our real handler for FunctionLeave notification
void CProfiler::Leave(FunctionID functionID, UINT_PTR clientData, COR_PRF_FRAME_INFO frameInfo, COR_PRF_FUNCTION_ARGUMENT_RANGE *argumentRange)
{
	
	EnterCriticalSection(&criticalSection);

	CFunctionInfo* functionInfo = NULL;
	std::map<FunctionID, CFunctionInfo*>::iterator iterF = m_functionMap.find(functionID);
	if (iterF != m_functionMap.end())
	{
		functionInfo = (iterF->second);
		
		/*if(	!functionInfo->toExclude
			(wcsncmp(functionInfo->GetFuncName(),pszApplicationName,wcslen(pszApplicationName))==0) 
				&&	(wcsncmp(functionInfo->GetFuncName(),L"System",6)!=0) 
				&&	(wcsncmp(functionInfo->GetFuncName(),L"Microsoft",9)!=0)
				)
		{*/
			ThreadID threadID;

			m_pICorProfilerInfo2->GetCurrentThreadID(&threadID);

			std::map<long,int>::iterator iterT = m_ThreadMap.find(threadID);

			if (iterT != m_ThreadMap.end())
			{
				if (iterT->second > 0)
					iterT->second--;
				
				if(functionInfo->GetFuncName() != NULL){
					char timeBuffer[20];
					GetTimestamp(timeBuffer);
					//Logging to file

					LogEvent(4,threadID,functionInfo->GetFuncName(),functionInfo->functionID,functionInfo->GetFuncName(),functionInfo->classID,-1,iterT->second,timeBuffer,functionInfo->GetFuncName(),functionInfo->isStatic);
				}
			}
		//}
	}
	
	LeaveCriticalSection(&criticalSection);
}

// our real handler for the FunctionTailcall notification
void CProfiler::Tailcall(FunctionID functionID, UINT_PTR clientData, COR_PRF_FRAME_INFO frameInfo)
{
	// decrement the call stack size
	if (m_callStackSize > 0)
		m_callStackSize--;
}

// ----  ICorProfilerCallback IMPLEMENTATION ------------------

STDMETHODIMP CProfiler::ThreadCreated(ThreadID threadID)
{
	EnterCriticalSection(&criticalSection);

	//Incrementing thread count
	m_threadNumber++;

	//Adding thread to thread map
	m_ThreadMap.insert(std::pair<long, int>(threadID, 0));

	char timeBuffer[20];
	GetTimestamp(timeBuffer);
	
	LogEvent(1,threadID,voidString,-1,voidString,-1,-1,-1,timeBuffer,voidString,0);
	
	LeaveCriticalSection(&criticalSection);

	return S_OK;
}

STDMETHODIMP CProfiler::ThreadDestroyed(ThreadID threadID)
{
	EnterCriticalSection(&criticalSection);

	//Decrementing thread count
	m_threadNumber--;

	//Removing thread from thread map
	m_ThreadMap.erase(threadID);

	char timeBuffer[20];
	GetTimestamp(timeBuffer);

	LogEvent(2,threadID,voidString,-1,voidString,-1,-1,-1,timeBuffer,voidString,0);
	
	LeaveCriticalSection(&criticalSection);

	return S_OK;
}

STDMETHODIMP CProfiler::ExceptionThrown(ObjectID objectID)
{
	
	EnterCriticalSection(&criticalSection);
	//Log di exception thrown
	char timeBuffer[20];
	GetTimestamp(timeBuffer);

	ThreadID threadID;
	
	m_pICorProfilerInfo2->GetCurrentThreadID(&threadID);

	LogEvent(5,threadID,voidString,-1,voidString,-1,objectID,-1,timeBuffer,voidString,0);
	
	LeaveCriticalSection(&criticalSection);

	return S_OK;
}

STDMETHODIMP CProfiler::ExceptionCatcherEnter(FunctionID functionId, ObjectID objectID)
{
	EnterCriticalSection(&criticalSection);

	char timeBuffer[20];
	GetTimestamp(timeBuffer);
	ThreadID threadID;
	
	m_pICorProfilerInfo2->GetCurrentThreadID(&threadID);

	LogEvent(6,threadID,voidString,functionId,voidString,-1,objectID,-1,timeBuffer,voidString,0);
	
	LeaveCriticalSection(&criticalSection);

	return S_OK;
}

STDMETHODIMP CProfiler::ExceptionCatcherLeave()
{
	EnterCriticalSection(&criticalSection);

	char timeBuffer[20];
	GetTimestamp(timeBuffer);

	ThreadID threadID;
	
	m_pICorProfilerInfo2->GetCurrentThreadID(&threadID);
	
	LogEvent(7,threadID,voidString,-1,voidString,-1,-1,-1,timeBuffer,voidString,0);
	
	LeaveCriticalSection(&criticalSection);

	return S_OK;
}

STDMETHODIMP CProfiler::GarbageCollectionStarted(int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason)
{
	EnterCriticalSection(&criticalSection);

	char timeBuffer[20];
	GetTimestamp(timeBuffer);
	
	LogString("[GCS],TIME=%s\r\n", timeBuffer);

	LeaveCriticalSection(&criticalSection);

	return S_OK;
}

STDMETHODIMP CProfiler::FinalizeableObjectQueued(DWORD finalizerFlags, ObjectID objectID)
{
	EnterCriticalSection(&criticalSection);

	ThreadID threadID;
	HRESULT hr = S_OK;
	char timeBuffer[20];

	GetTimestamp(timeBuffer);
	hr = m_pICorProfilerInfo2->GetCurrentThreadID(&threadID);

	if(SUCCEEDED(hr))
	{
		LogString("[OF],TIME=%s,THREAD_ID=%ld,OBJ_ID=%ld\r\n", timeBuffer, threadID, objectID);
	}
	
	LeaveCriticalSection(&criticalSection);

	return hr;
}

STDMETHODIMP CProfiler::GarbageCollectionFinished()
{
	EnterCriticalSection(&criticalSection);

	char timeBuffer[20];
	GetTimestamp(timeBuffer);
	
	LogString("[GCF],TIME=%s\r\n", timeBuffer);

	LeaveCriticalSection(&criticalSection);

	return S_OK;
}

STDMETHODIMP CProfiler::ObjectAllocated(ObjectID objectID, ClassID classID)
{
	EnterCriticalSection(&criticalSection);
	
	ThreadID threadID;
	HRESULT hr = S_OK;
	WCHAR szClass[NAME_BUFFER_SIZE];
	ULONG cchClass, pcSize = 0;
	char timeBuffer[20];
	IMetaDataImport* pIMetaDataImport = 0;

	GetTimestamp(timeBuffer);
	m_pICorProfilerInfo2->GetCurrentThreadID(&threadID);

	//Gets the parent module and the metadata token for the specified class
	ModuleID pModuleId;
	mdTypeDef pTypeDefToken;

	hr = m_pICorProfilerInfo2->GetObjectSize(objectID, &pcSize);

	if (!SUCCEEDED(hr))
		pcSize = 0;
	
	hr = m_pICorProfilerInfo2->GetClassIDInfo(classID, &pModuleId, &pTypeDefToken);

	if(SUCCEEDED(hr))
	{
		hr = m_pICorProfilerInfo2->GetModuleMetaData(pModuleId, 0, IID_IMetaDataImport, (LPUNKNOWN *) &pIMetaDataImport);

		if(SUCCEEDED(hr))
		{
			LogEvent(9,threadID,voidString,pModuleId,voidString,classID,objectID,pcSize,timeBuffer,voidString,0);
		}
	}

	LeaveCriticalSection(&criticalSection);

	return S_OK;
}


// called when the profiling object is created by the CLR
STDMETHODIMP CProfiler::Initialize(IUnknown *pICorProfilerInfoUnk)
{
    DWORD dwRet, dwErr;
	char *zErrMsg = 0;
	char insertText[256];
	SYSTEMTIME *datetime = new SYSTEMTIME;
	int rc;
	const char      *data = NULL;
    sqlite3_stmt    *stmt = NULL;

	// make a critical section for synchronization
	InitializeCriticalSection(&criticalSection);
	
	InitializeCriticalSection(&criticalSectionDBwrite);

	// set up our global access pointer



	pszApplicationName = (LPTSTR) malloc(ENVIRONMENT_MAX_BUFFER_SIZE*sizeof(TCHAR));
	if(NULL == pszApplicationName)
    {
		return S_FALSE;
    }


    dwRet = GetEnvironmentVariable(PROFILER_APPLICATION_NAME, pszApplicationName, ENVIRONMENT_MAX_BUFFER_SIZE);
	
	if(0 == dwRet)
	{
		return S_FALSE;
	}

	pszApplicationPath = (LPTSTR) malloc(ENVIRONMENT_MAX_BUFFER_SIZE*sizeof(TCHAR));
	if(NULL == pszApplicationPath)
    {
		return S_FALSE;
    }

    dwRet = GetEnvironmentVariable(PROFILER_APPLICATION_PATH, pszApplicationPath, ENVIRONMENT_MAX_BUFFER_SIZE);
	
	if(0 == dwRet)
	{
		return S_FALSE;
	}

	g_pICorProfilerCallback = this;

	rc = sqlite3_open("database.db", &database);
	if( rc ){
		sqlite3_close(database);
	}else{
		
		// check if the database has tables, otherwise create it with above's, or chek IF EXISTS clause of SQL (maybe not implemented yet :( )

		rc = sqlite3_exec(database, "PRAGMA synchronous=OFF", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "PRAGMA count_changes=OFF", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "PRAGMA journal_mode=MEMORY", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "PRAGMA temp_store=MEMORY", callback, 0, &zErrMsg);

		rc = sqlite3_exec(database, "CREATE TABLE applications ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, path TEXT NOT NULL, time TEXT NOT NULL );", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "CREATE TABLE dllModules ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, name TEXT NOT NULL, path TEXT );", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "CREATE TABLE events ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, eventId INTEGER NOT NULL, idThread TEXT, nameThread TEXT, idFunction TEXT, nameFunction TEXT, idClass TEXT, idObject TEXT, stackDepth INTEGER, timestamp TEXT, exceptionName TEXT, static BOOL DEFAULT 0, idApplication INTEGER );", callback, 0, &zErrMsg);
		

		// eventsType
		rc = sqlite3_exec(database, "CREATE TABLE eventsType ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, eventName TEXT NOT NULL, callbackApi TEXT NOT NULL, EventDescription TEXT, [default] BOOL NOT NULL, interface TEXT );", callback, 0, &zErrMsg);
		//insert data for eventsType
		rc = sqlite3_exec(database, "CREATE TABLE eventsType ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, eventName TEXT NOT NULL, callbackApi TEXT NOT NULL, EventDescription TEXT,default BOOL NOT NULL ,interface TEXT);", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "INSERT INTO eventsType VALUES(1,'new_thread','ThreadCreated','Notifies the profiler that a thread has been created','true','IcorProfilerCallback');", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "INSERT INTO eventsType VALUES(2,'destroy_thread','ThreadDestroyed','Notifies the profiler that a thread has been destroyed','true','IcorProfilerCallback');", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "INSERT INTO eventsType VALUES(3,'enter_function','Enter[maybe]','Notifies the profiler that a function has been called','true','IcorProfilerCallback');", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "INSERT INTO eventsType VALUES(4,'leave_function','Leave[maybe]','Notifies the profiler that a function has been   ','true','IcorProfilerCallback');", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "INSERT INTO eventsType VALUES(5,'exception_thrown','ExceptionThrown','Notifies the profiler that an exception has been thrown','true','IcorProfilerCallback');", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "INSERT INTO eventsType VALUES(6,'exception_catcher_enter','ExceptionCatcherEnter','Notifies the profiler that control is being passed to the appropriate catch block','true','IcorProfilerCallback');", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "INSERT INTO eventsType VALUES(7,'exception_catcher_leave','ExceptionCatcherLeave','Notifies the profiler that control is being passed out of the appropriate catch block','true','IcorProfilerCallback');", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "INSERT INTO eventsType VALUES(8,'shutdown','Shutdown','Notifies the profiler that the application is shutting down','true','IcorProfilerCallback');", callback, 0, &zErrMsg);
		rc = sqlite3_exec(database, "INSERT INTO eventsType VALUES(9,'object_allocated','ObjectCreated','Notifies the profiler that the application allocated a new object','true','IcorProfilerCallback');", callback, 0, &zErrMsg);
		
		//tempo zero
		GetSystemTime(datetime);
		sprintf(insertText, "INSERT INTO applications(name,path,time) VALUES('%ls','%ls','%4d%2d%2d %2d:%2d:%2d');", pszApplicationName, pszApplicationPath, datetime->wYear, datetime->wMonth, datetime->wDay, datetime->wHour, datetime->wMinute, datetime->wSecond);
		delete datetime;

		rc = sqlite3_exec(database, insertText, callback, 0, &zErrMsg);

		//applicationId = (sqlite3_int64)sqlite3_last_insert_rowid(database);

		
		rc = sqlite3_exec(database, "SELECT last_insert_rowid();", callbackLastIdApplication, 0, &zErrMsg);
		
		rc = sqlite3_exec(database, "SELECT MAX(id) FROM events;", callbackLastIdEvent, 0, &zErrMsg);
		

		/*if(rc== SQLITE_OK)
		{
			while( sqlite3_step( stmt ) == SQLITE_ROW ) {
				applicationId = (int)(sqlite3_int64)sqlite3_column_int64( stmt, 0 );
				LogString("[ID ID ID] %d\r\n",applicationId);
			}
		}else{
			applicationId = -1;
		}*/

	}

	// get the ICorProfilerInfo interface
	HRESULT hr = pICorProfilerInfoUnk->QueryInterface(IID_ICorProfilerInfo, (LPVOID*)&m_pICorProfilerInfo);
	if (FAILED(hr))
		return E_FAIL;
	// determine if this object implements ICorProfilerInfo2
	hr = pICorProfilerInfoUnk->QueryInterface(IID_ICorProfilerInfo2, (LPVOID*)&m_pICorProfilerInfo2);
	if (FAILED(hr))
	{
		// we still want to work if this call fails, might be an older .NET version
		m_pICorProfilerInfo2.p = NULL;
	}

	// Indicate which events we're interested in.
	hr = SetEventMask();
	if (FAILED(hr))
		LogString("[ERROR] Error setting the event mask\r\n");

	// set the enter, leave and tailcall hooks
	if (m_pICorProfilerInfo2.p == NULL)
	{
		// note that we are casting our functions to the definitions for the callbacks
		hr = m_pICorProfilerInfo->SetEnterLeaveFunctionHooks((FunctionEnter*)&FunctionEnterNaked, (FunctionLeave*)&FunctionLeaveNaked, (FunctionTailcall*)&FunctionTailcallNaked);
		if (SUCCEEDED(hr))
			hr = m_pICorProfilerInfo->SetFunctionIDMapper((FunctionIDMapper*)&FunctionMapper);
	}
	else
	{
		hr = m_pICorProfilerInfo2->SetEnterLeaveFunctionHooks2(FunctionEnterNaked, FunctionLeaveNaked, FunctionTailcallNaked);
		if (SUCCEEDED(hr))
			hr = m_pICorProfilerInfo2->SetFunctionIDMapper(FunctionMapper);
	}
	// report our success or failure to the log file
	if (FAILED(hr))
		LogString("[ERROR] Error setting the enter, leave and tailcall hooks\r\n");
#ifdef VERBOSE_MODE
	else
		LogString("[INFO] Successfully initialized profiling\r\n" );
#endif

//Save initial performance counter values and frequency
	//http://msdn.microsoft.com/en-us/library/ms644905(VS.85).aspx
	QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
	QueryPerformanceCounter((LARGE_INTEGER *)&start);

	return S_OK;
}

// called when the profiler is being terminated by the CLR
STDMETHODIMP CProfiler::Shutdown()
{
	// log the we're shutting down
	LogString("[INFO] Shutdown...\r\n" );

	// write the function names and call counts to the output file
	std::map<FunctionID, CFunctionInfo*>::iterator iter;
	for (iter = m_functionMap.begin(); iter != m_functionMap.end(); iter++)
	{
		// log the function's info
		CFunctionInfo* functionInfo = iter->second;
		//LogString("[INFO] %s : call count = %d\r\n", functionInfo->GetName(), functionInfo->GetCallCount());
		// free the memory for the object
		delete iter->second;
	}
	// clear the map
	m_functionMap.clear();

	// tear down our global access pointers
	g_pICorProfilerCallback = NULL;

	if(database!=NULL) sqlite3_close(database);

	return S_OK;
}

void CProfiler::LoadSettings(){}


// Creates the log file.  It uses the LOG_FILENAME environment variable if it 
// exists, otherwise it creates the file "ICorProfilerCallback Log.log" in the 
// executing directory.  This function doesn't report success or not because 
// LogString always checks for a valid file handle whenever the file is written
// to.
void CProfiler::CreateLogFile()
{
	// get the log filename
	memset(m_logFileName, 0, sizeof(m_logFileName));
	// get the log file name (stored in an environment var)
	if (GetEnvironmentVariable(_T("LOG_FILENAME"), m_logFileName, _MAX_PATH) == 0)
	{
		// just write to "ICorProfilerCallback Log.log"
		_tcscpy(m_logFileName, _T("ICorProfilerCallback Log.log"));
	}
	// delete any existing log file
	::DeleteFile(m_logFileName);
	// set up log files in the current working directory
	TCHAR appendName[_MAX_PATH];
	
	for (int i = 0; i < NUMFILES; i++){
		memset(appendName, 0, sizeof(appendName));

		std::swprintf(appendName, _MAX_PATH, L"%s%d.txt", m_logFileName, i);
		
		m_hLogFile[i] = CreateFile(appendName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	}
}

// Closes the log file
void CProfiler::CloseLogFile()
{

	TCHAR appendName[_MAX_PATH];
	for (int i = 0; i < NUMFILES; i++){
		memset(appendName, 0, sizeof(appendName));
		std::swprintf(appendName, _MAX_PATH, L"%s%d.txt", m_logFileName, i);
		// close the log file
		if (appendName != INVALID_HANDLE_VALUE)
		{
			CloseHandle(appendName);
			m_hLogFile[i] = INVALID_HANDLE_VALUE;
		}
	}
}

void CProfiler::GetTimestamp(char *buffer)
{
	double nano = 1000000000;
	//Get the current value of performance counter
	QueryPerformanceCounter((LARGE_INTEGER *)&finish);

	sprintf(buffer,"%.0lf",((finish-start)* (1.0 / freq))*nano);
}

// Writes a string to the log file.  Uses the same calling convention as printf.
void CProfiler::LogString(char *pszFmtString, ...)
{
	CHAR szBuffer[4096], szBuffer1[4096]; DWORD dwWritten = 0;
	
	//When I reach maximum number of lines in a single file, switch to next
	if (m_lines == MAXLINES)
	{
		if (m_fileNumber < NUMFILES)
			m_fileNumber ++;
		else
			m_fileNumber = 0;
		m_lines = 0; //new file, restart lines count
	}

	
	if(m_hLogFile[m_fileNumber] != INVALID_HANDLE_VALUE)
	{
		va_list args;
		va_start( args, pszFmtString );
		vsprintf(szBuffer, pszFmtString, args );
		va_end( args );

		strcpy(szBuffer1, szBuffer);

		// write out to the file if the file is open
		WriteFile(m_hLogFile[m_fileNumber], szBuffer, (DWORD)strlen(szBuffer), &dwWritten, NULL);
		m_lines++;
	}

	EnterCriticalSection(&criticalSection);
	//Scrivo su un'altro file di prova
	

	if (_fakeLog != NULL)
	{
		WriteFile(_fakeLog, szBuffer, (DWORD)strlen(szBuffer1), &dwWritten, NULL);
	}

	

	LeaveCriticalSection(&criticalSection);
}

///<summary>
// We are monitoring events that are interesting for determining
// the hot spots of a managed CLR program (profilee). This includes
// thread related events, function enter/leave events, exception 
// related events, and unmanaged/managed transition events. Note 
// that we disable inlining. Although this does indeed affect the 
// execution time, it provides better accuracy for determining
// hot spots.
//
// If the system does not support high precision counters, then
// do not profile anything. This is determined in the constructor.
///</summary>
HRESULT CProfiler::SetEventMask()
{
	//COR_PRF_MONITOR_NONE	= 0,
	//COR_PRF_MONITOR_FUNCTION_UNLOADS	= 0x1,
	//COR_PRF_MONITOR_CLASS_LOADS	= 0x2,
	//COR_PRF_MONITOR_MODULE_LOADS	= 0x4,
	//COR_PRF_MONITOR_ASSEMBLY_LOADS	= 0x8,
	//COR_PRF_MONITOR_APPDOMAIN_LOADS	= 0x10,
	//COR_PRF_MONITOR_JIT_COMPILATION	= 0x20,
	//COR_PRF_MONITOR_EXCEPTIONS	= 0x40,
	//COR_PRF_MONITOR_GC	= 0x80,
	//COR_PRF_MONITOR_OBJECT_ALLOCATED	= 0x100,
	//COR_PRF_MONITOR_THREADS	= 0x200,
	//COR_PRF_MONITOR_REMOTING	= 0x400,
	//COR_PRF_MONITOR_CODE_TRANSITIONS	= 0x800,
	//COR_PRF_MONITOR_ENTERLEAVE	= 0x1000,
	//COR_PRF_MONITOR_CCW	= 0x2000,
	//COR_PRF_MONITOR_REMOTING_COOKIE	= 0x4000 | COR_PRF_MONITOR_REMOTING,
	//COR_PRF_MONITOR_REMOTING_ASYNC	= 0x8000 | COR_PRF_MONITOR_REMOTING,
	//COR_PRF_MONITOR_SUSPENDS	= 0x10000,
	//COR_PRF_MONITOR_CACHE_SEARCHES	= 0x20000,
	//COR_PRF_MONITOR_CLR_EXCEPTIONS	= 0x1000000,
	//COR_PRF_MONITOR_ALL	= 0x107ffff,
	//COR_PRF_ENABLE_REJIT	= 0x40000,
	//COR_PRF_ENABLE_INPROC_DEBUGGING	= 0x80000,
	//COR_PRF_ENABLE_JIT_MAPS	= 0x100000,
	//COR_PRF_DISABLE_INLINING	= 0x200000,
	//COR_PRF_DISABLE_OPTIMIZATIONS	= 0x400000,
	//COR_PRF_ENABLE_OBJECT_ALLOCATED	= 0x800000,
	// New in VS2005
	//	COR_PRF_ENABLE_FUNCTION_ARGS	= 0x2000000,
	//	COR_PRF_ENABLE_FUNCTION_RETVAL	= 0x4000000,
	//  COR_PRF_ENABLE_FRAME_INFO	= 0x8000000,
	//  COR_PRF_ENABLE_STACK_SNAPSHOT	= 0x10000000,
	//  COR_PRF_USE_PROFILE_IMAGES	= 0x20000000,
	// End New in VS2005
	//COR_PRF_ALL	= 0x3fffffff,
	//COR_PRF_MONITOR_IMMUTABLE	= COR_PRF_MONITOR_CODE_TRANSITIONS | COR_PRF_MONITOR_REMOTING | COR_PRF_MONITOR_REMOTING_COOKIE | COR_PRF_MONITOR_REMOTING_ASYNC | COR_PRF_MONITOR_GC | COR_PRF_ENABLE_REJIT | COR_PRF_ENABLE_INPROC_DEBUGGING | COR_PRF_ENABLE_JIT_MAPS | COR_PRF_DISABLE_OPTIMIZATIONS | COR_PRF_DISABLE_INLINING | COR_PRF_ENABLE_OBJECT_ALLOCATED | COR_PRF_ENABLE_FUNCTION_ARGS | COR_PRF_ENABLE_FUNCTION_RETVAL | COR_PRF_ENABLE_FRAME_INFO | COR_PRF_ENABLE_STACK_SNAPSHOT | COR_PRF_USE_PROFILE_IMAGES

	// set the event mask 
	
	DWORD eventMask = (DWORD)COR_PRF_MONITOR_ENTERLEAVE
						| (DWORD) COR_PRF_MONITOR_THREADS
						| (DWORD) COR_PRF_ENABLE_FRAME_INFO
						| (DWORD) COR_PRF_ENABLE_FUNCTION_ARGS
						| (DWORD) COR_PRF_MONITOR_EXCEPTIONS
						| (DWORD) COR_PRF_MONITOR_GC
						| (DWORD) COR_PRF_ENABLE_OBJECT_ALLOCATED
						| (DWORD) COR_PRF_MONITOR_OBJECT_ALLOCATED;
	

	/*DWORD eventMask =	 (DWORD) COR_PRF_MONITOR_ENTERLEAVE 
						|(DWORD) COR_PRF_MONITOR_THREADS
						|(DWORD) COR_PRF_MONITOR_EXCEPTIONS 
						|(DWORD) COR_PRF_ENABLE_FUNCTION_ARGS
						|(DWORD) COR_PRF_ENABLE_FRAME_INFO;*/

	return m_pICorProfilerInfo->SetEventMask(eventMask);
}

// creates the fully scoped name of the method in the provided buffer
HRESULT CProfiler::GetMethodInfo(CFunctionInfo *functionInfo)
{
	IMetaDataImport* pIMetaDataImport = 0;
	HRESULT hr = S_OK;
	mdToken funcToken = 0;
	WCHAR szFunction[NAME_BUFFER_SIZE];
	WCHAR szClass[NAME_BUFFER_SIZE];
	WCHAR szModule[NAME_BUFFER_SIZE];

	hr = m_pICorProfilerInfo->GetTokenAndMetaDataFromFunction(functionInfo->functionID, IID_IMetaDataImport, (LPUNKNOWN *) &pIMetaDataImport, &funcToken);
	if(SUCCEEDED(hr))
	{
		mdTypeDef classTypeDef;
		ULONG cchFunction;
		ULONG cchClass;
		ULONG cchModule;

		hr = pIMetaDataImport->GetMethodProps(funcToken, &classTypeDef, szFunction, NAME_BUFFER_SIZE, &cchFunction, 0, 0, 0, 0, 0);
		if (SUCCEEDED(hr))
		{

			functionInfo->SetFuncName(szFunction);

			hr = pIMetaDataImport->GetTypeDefProps(classTypeDef, szClass, NAME_BUFFER_SIZE, &cchClass, 0, 0);

			if (SUCCEEDED(hr))
			{
				functionInfo->SetClassName(szClass);

				mdModule pmd;
				
				hr = pIMetaDataImport->GetModuleFromScope(&pmd);

				if (SUCCEEDED(hr))
				{

					hr = pIMetaDataImport->GetModuleRefProps(pmd, szModule, NAME_BUFFER_SIZE, &cchModule);

					if (SUCCEEDED(hr))
					{
						
						functionInfo->SetModuleName(szModule);

						LogString("%ls\n",szModule);

						if (		wcsncmp(szModule, L"kernel32", 8) == 0 
								||	wcsncmp(szModule, L"gdi32", 5) == 0 
								||	wcsncmp(szModule, L"winmm", 5) == 0 
								||	wcsncmp(szModule, L"System", 6) == 0  
								||	wcsncmp(szModule, L"Microsoft", 9) == 0 )
						{
							functionInfo->toExclude = true;
							return hr;
						}
						else
						{
							functionInfo->toExclude = false;
						}
					}
				}
			}
		}

		pIMetaDataImport->Release();

		functionInfo->isStatic = IsMdStatic(cchFunction);

	}

	return hr;
}

void CProfiler::LogEvent(	int		eventType,		long	idThread, 		WCHAR*	nameThread, 	long	idFunction, 
							WCHAR*	nameFunction,	long	idClass,		long	idObject,		long	stackDepth,
							char*	timeBuffer,		WCHAR*	exceptionName,	bool	isStatic){
	
	char insertText[4096];
	char *zErrMsg = 0;
	int rc;

	EnterCriticalSection(&criticalSectionDBwrite);

	if(isStatic){
		sprintf(insertText,"INSERT INTO events(id,eventId,idThread,nameThread,idFunction,nameFunction,idClass,idObject,stackDepth,timestamp,exceptionName,static,idApplication) VALUES (%d, %d,'%ld','%ls','%ld','%ls','%ld',%ld,%ld,'%I64u',NULL,0,%d);",++eventId,eventType,idThread,nameThread,idFunction,nameFunction,idClass,idObject,stackDepth,timeBuffer,applicationId,applicationId);
	}else{
		sprintf(insertText,"INSERT INTO events(id,eventId,idThread,nameThread,idFunction,nameFunction,idClass,idObject,stackDepth,timestamp,exceptionName,static,idApplication) VALUES (%d, %d,'%ld','%ls','%ld','%ls','%ld',%ld,%ld,'%I64u',NULL,1,%d);",++eventId,eventType,idThread,nameThread,idFunction,nameFunction,idClass,idObject,stackDepth,timeBuffer,applicationId,applicationId);
	}
	LogString("[SQL] %s\r\n", insertText);
																																																						 
	if(database!=NULL){
		
		rc = sqlite3_exec(database, insertText, callback, 0, &zErrMsg);

	}
	
	LeaveCriticalSection(&criticalSectionDBwrite);

}																																																						
																																																						
																																																						
																																																						
																																																						 